package mule;

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.DataLine.Info;
import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Main Game class for the MULE Game
 * 
 */
public class Game extends JFrame implements Serializable {

	static Random rand;
	static boolean randomOccured;
	static int gameLevel;
	static Map gameMap;
	static Player[] playerArray;
	static Player player;
	static int roundNumber;
	static int foodRequired;
	static int players;
	static int current;
	static int passes;
	static int turns;
	static Timer timer;
	static int time;
	static private Comparator<Player> moneyComparator;
	static {
		moneyComparator = new Comparator<Player>() {
			@Override
			public int compare(Player a, Player b) {
				return a.getMoney() - b.getMoney();
			}
		};
	}

	/**
	 * Constructor to load a saved game
	 */
	public Game(boolean selection, int level, Map map, ArrayList<Player> arr,
			int round) {
		playerArray = new Player[arr.size()];
		for (int i = 0; i < arr.size(); i++) {
			playerArray[i] = arr.get(i);
		}
		randomOccured = false;
		gameMap = map;
		gameLevel = level;
		players = arr.size();
		foodRequired = 3;
		current = 0;
		roundNumber = round;
		time = 50;
		turns = 0;
		setPlayerOrder();
		add(new Board());
		setTitle("M.U.L.E.");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(1080, 720);
		setVisible(true);
		setLocationRelativeTo(null);
		setResizable(false);
		if (!selection) {
			Map.selectionPhase = false;
		}
	}

	/**
	 * Initializes the characteristics of the game window
	 */
	public Game(int level, Map map, int numPlayers, ArrayList<Player> arr) {
		playerArray = new Player[arr.size()];
		for (int i = 0; i < arr.size(); i++) {
			playerArray[i] = arr.get(i);
		}
		randomOccured = false;
		gameLevel = level;
		gameMap = map;
		players = numPlayers;
		foodRequired = 3;
		current = 0;
		roundNumber = 1;
		time = 50;
		turns = 0;
		setPlayerOrder();
		calculateProduction();
		add(new Board());
		setTitle("M.U.L.E");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(1080, 720);
		setVisible(true);
		setLocationRelativeTo(null);
		setResizable(false);
	}

	/**
	 * Gets the current player
	 * 
	 * @return current player
	 */
	public static Player getCurrentPlayer() {
		return playerArray[current];
	}

	/**
	 * Switches to the next player's turn
	 * 
	 * @param none
	 * @return none
	 */
	public static void switchPlayer() {
		if (current == players - 1) {
			player = playerArray[0];
			current = 0;
			roundNumber++;
			if (roundNumber < 5) {
				foodRequired = 3;
			} else if (roundNumber < 9) {
				foodRequired = 4;
			} else {
				foodRequired = 5;
			}
		} else {
			current++;
			player = playerArray[current];
		}
		if (Map.landOfficePhase) {
			Map.landOfficePhase = false;
		}
		turns++;
		bottomPanel.updateInfo();
		calculateProduction();
		setPlayerOrder();
	}

	/**
	 * The board is a panel, where the game takes place
	 */
	class Board extends JPanel {
		public Board() {
			setLayout(new GridLayout(2, 1));
			add(gameMap);
			add(new bottomPanel());
		}
	}

	/**
	 * Bottom Panel of the MULE Game Panel, Contains player statistics and
	 * finish turn button
	 */
	static class bottomPanel extends JPanel {

		static JLabel currentRound;
		static JLabel currentPlayer;
		static JLabel money;
		static JLabel food;
		static JLabel energy;
		static JLabel ore;
		static JLabel crystite;
		static JLabel mule;
		static JLabel timeLabel;
		JButton finishTurn;
		JButton saveGame;
		JButton productionButton;

		/**
		 * Construct the bottom panel
		 */
		public bottomPanel() {

			timer = new Timer(1000, new action());
			timer.setActionCommand("timer");
			timer.start();
			player = playerArray[current];

			rand = new Random();
			int randomInt = rand.nextInt(100) + 1;
			if (randomInt <= 27 && !Map.selectionPhase) {
				RandomEvent event = new RandomEvent(player);
				int i = event.getRandomEvent();
			}

			setLayout(new GridLayout(10, 1));
			currentRound = new JLabel("Selection phase " + roundNumber);
			currentPlayer = new JLabel("Current player: " + player.getName());
			money = new JLabel("Money: " + player.getMoney());
			food = new JLabel("Food: " + player.getFood());
			energy = new JLabel("Energy: " + player.getEnergy());
			ore = new JLabel("Ore: " + player.getOre());
			crystite = new JLabel("Crystite: ");
			mule = new JLabel("Mule: ");
			timeLabel = new JLabel("Time left: " + time);
			finishTurn = new JButton("Finish turn");
			saveGame = new JButton("Save");
			productionButton = new JButton("View Production");

			/*
			 * timer.schedule(new TimerTask() { public void run() { } },
			 * 1000*60*20);
			 */

			finishTurn.addActionListener(new action());
			saveGame.addActionListener(new SaveList());
			productionButton.addActionListener(new production());
			add(currentRound);
			add(currentPlayer);
			add(money);
			add(food);
			add(energy);
			add(ore);
			add(crystite);
			add(mule);
			add(finishTurn);
			add(timeLabel);
			add(saveGame);
			add(productionButton);
		}

		/**
		 * Action listener for save button
		 * 
		 * @author kevincurtin
		 * 
		 */
		class SaveList implements ActionListener {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				String gameName = JOptionPane
						.showInputDialog("Name this save file");
				saveGame(gameName);
			}

		}
		
		
		class action implements ActionListener {
			/**
			 * Actionlistener for the Finish Turn button, if all players choose
			 * finish consecutively, land selection phase ends
			 */
			@Override
			public void actionPerformed(ActionEvent arg0) {
				if (!Map.selectionPhase) {
					if (arg0.getActionCommand().equals("timer") && time == 0) {
						JOptionPane.showMessageDialog(null, player.getName()
								+ " has run out of time");
						timer.stop();

						player.setScore(player.getMoney());
						switchPlayer();
						updateInfo();
						passes++;
						if (passes == players && Map.selectionPhase) {
							gameMap.selectionPhaseOff();
						}
						if (player.getFood() >= foodRequired) {
							player.setTurnTime(50);
						} else if (player.getFood() <= 0) {
							player.setTurnTime(5);
						} else {
							player.setTurnTime(30);
						}
						time = player.getTurnTime();
						timer.restart();
						randomOccured = false;

					} else if (arg0.getActionCommand().equals("timer")) {
						time--;
						updateInfo();
					}
					int randomInt = rand.nextInt(100) + 1;
					if (randomInt <= 27 && randomOccured == false) {
						RandomEvent event = new RandomEvent(player);
						int i = event.getRandomEvent();
					}
					randomOccured = true;
				}

				if (arg0.getActionCommand().equals("Finish turn")) {
					player.setScore(player.getMoney());
					switchPlayer();
					updateInfo();
					passes++;
					if (passes == players && Map.selectionPhase) {
						gameMap.selectionPhaseOff();
					}
					if (player.getFood() >= foodRequired) {
						player.setTurnTime(50);
					} else if (player.getFood() <= 0) {
						player.setTurnTime(5);
					} else {
						player.setTurnTime(30);
					}
					if (!Map.selectionPhase) {
						time = player.getTurnTime();
						timer.restart();
						randomOccured = false;
					}
				}

			}
		}
		
		class production implements ActionListener{
			@Override
			public void actionPerformed(ActionEvent arg0){
				productionFrame produce = new productionFrame();
			}
			
		}
		class productionFrame extends JFrame {
			
			/**
			 * Displays the production frame which shows how many resources the player is producing
			 * 
			 */
			public productionFrame(){
				setTitle("Production");
				setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
				setSize(400, 300);
				setVisible(true);
				setLocationRelativeTo(null);
				setResizable(false);
				JPanel list = new JPanel();
				list.setLayout(new GridLayout(4,1));
				JLabel dialog, foodProduction, energyProduction, oreProduction;
				int food, energy, ore;
				food = 0; energy = 0; ore = 0;
				dialog = new JLabel("<html>How many resources you are producing is shown below.<br>It takes one energy per mule to produce resources.<br>If you have 0 energy you are unable to produce anything.</html>");
				foodProduction = new JLabel("Currently producing: " + food + " food");
				energyProduction = new JLabel("Currently producing: " + energy + " energy");
				oreProduction = new JLabel("Currently producing: " + ore + " ore");
				add(list);
				list.add(dialog);
				list.add(foodProduction);
				list.add(energyProduction);
				list.add(oreProduction);
				
				if(player.getOwnedTiles().size() >= 1){
					if(player.getEnergy() == 0){
						foodProduction.setText("Currently producing: 0 food");
						energyProduction.setText("Currently producing: 0 energy");
						oreProduction.setText("Currently producing: 0 ore");
					}
					else {
						for(int i = 0; i < player.getOwnedTiles().size(); i++){
							Tile land = player.getOwnedTiles().get(i);
							if(land.hasMule() && land.getMule().toString() == "Food Mule" && player.getEnergy() > 0){
								food += land.getFoodRate();
								energy -= 1;
							}
							else if(land.hasMule() && land.getMule().toString() == "Energy Mule" && player.getEnergy() > 0)
								energy += land.getEnergyRate()-1;
							else if(land.hasMule() && land.getMule().toString() == "Ore Mule" && player.getEnergy() > 0){
								ore += land.getOreRate();
								energy -= 1;
							}
							foodProduction.setText("Currently producing: " + food + " food");
							energyProduction.setText("Currently producing: " + energy + " energy");
							oreProduction.setText("Currently producing: " + ore + " ore");
						}
					}
					
				}
			} 
		
		}

		/**
		 * Updates the Labels on the Game Panel to display current player's
		 * statistics
		 * 
		 * @param none
		 * @return none
		 * 
		 */
		public static void updateInfo() {
			if (Map.selectionPhase) {
				currentRound.setText("Selection phase " + roundNumber);
			} else {
				currentRound.setText("Round " + roundNumber);
			}
			currentPlayer.setText("Current player: " + player.getName());
			money.setText("Money: " + player.getMoney());
			food.setText("Food: " + player.getFood());
			energy.setText("Energy: " + player.getEnergy());
			ore.setText("Ore: " + player.getOre());
			if (player.hasMule()) {
				mule.setText("Mule: " + player.getMule());
			} else {
				mule.setText("Mule:");
			}
			timeLabel.setText("Time left: " + time);
		}

	}

	/**
	 * This method sets the "passes" int back to 0 each time a tile is
	 * purchased, if all players press "Finish Turn" consecutively, land
	 * selection phase will end
	 * 
	 * @param none
	 * @return none
	 */
	public static void resetPasses() {
		passes = 0;// reset passes to 0, we only want to end land
		// selection when all players pass in same round
	}

	/**
	 * Sets turn order at the start of the game and at the start of every round
	 */
	public static void setPlayerOrder() {
		if (current == players - 1) {
			Arrays.sort(playerArray, moneyComparator);

		}
	}

	public static void calculateProduction() {
		if (current == players - 1) {
			for (int i = 0; i < Map.totalOwnedTiles.size(); i++) {
				Map.totalOwnedTiles.get(i).production();
			}
		}
	}
	
	

	public static void saveGame(String gameName) {

		try {

			DocumentBuilderFactory docFactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

			// root elements
			Document doc = docBuilder.newDocument();
			Element rootElement = doc.createElement("game");
			doc.appendChild(rootElement);

			// player elements
			for (int x = 0; x < players; x++) {
				Player currentPlayer = playerArray[x];
				Element players = doc.createElement("Player");
				rootElement.appendChild(players);

				// set attribute to staff element
				Attr attr = doc.createAttribute("id");
				attr.setValue(x + "");
				players.setAttributeNode(attr);

				// shorten way
				// staff.setAttribute("id", "1");

				// firstname elements
				Element name = doc.createElement("name");
				name.appendChild(doc.createTextNode(currentPlayer.getName()));
				players.appendChild(name);

				Element race = doc.createElement("race");
				race.appendChild(doc.createTextNode(""
						+ currentPlayer.getRace()));
				players.appendChild(race);

				// lastname elements
				Element red = doc.createElement("color-red");
				red.appendChild(doc.createTextNode(""
						+ currentPlayer.getColor().getRed()));
				players.appendChild(red);
				Element green = doc.createElement("color-green");
				green.appendChild(doc.createTextNode(""
						+ currentPlayer.getColor().getGreen()));
				players.appendChild(green);
				Element blue = doc.createElement("color-blue");
				blue.appendChild(doc.createTextNode(""
						+ currentPlayer.getColor().getBlue()));
				players.appendChild(blue);

				// nickname elements
				Element score = doc.createElement("score");
				score.appendChild(doc.createTextNode(""
						+ currentPlayer.getScore()));
				players.appendChild(score);

				// salary elements
				Element money = doc.createElement("money");
				money.appendChild(doc.createTextNode(""
						+ currentPlayer.getMoney()));
				players.appendChild(money);

				Element food = doc.createElement("food");
				food.appendChild(doc.createTextNode(""
						+ currentPlayer.getFood()));
				players.appendChild(food);
				Element energy = doc.createElement("energy");
				energy.appendChild(doc.createTextNode(""
						+ currentPlayer.getEnergy()));
				players.appendChild(energy);
				Element ore = doc.createElement("ore");
				ore.appendChild(doc.createTextNode("" + currentPlayer.getOre()));
				players.appendChild(ore);
				Element crystite = doc.createElement("crystite");
				crystite.appendChild(doc.createTextNode(""
						+ currentPlayer.getCrystite()));
				players.appendChild(crystite);
				Element turnTime = doc.createElement("turnTime");
				turnTime.appendChild(doc.createTextNode(""
						+ currentPlayer.getTurnTime()));
				players.appendChild(turnTime);
				Element ownedTiles = doc.createElement("ownedTiles");
				for (Tile t : currentPlayer.getOwnedTiles()) {
					Element tile = doc.createElement("tile");
					ownedTiles.appendChild(tile);
					tile.appendChild(doc.createTextNode(t.getPoint().getX()
							+ "," + t.getPoint().getY() + "," + t.getMule()));
					players.appendChild(ownedTiles);
				}
				// players.appendChild(ownedTiles);
			}
			Element difficultyNode = doc.createElement("difficulty");
			difficultyNode.appendChild(doc.createTextNode("" + gameLevel));
			rootElement.appendChild(difficultyNode);
			Element roundNode = doc.createElement("round");
			roundNode.appendChild(doc.createTextNode("" + turns));
			rootElement.appendChild(roundNode);
			Element selectionNode = doc.createElement("selection");
			selectionNode.appendChild(doc.createTextNode(String
					.valueOf(Map.selectionPhase)));
			rootElement.appendChild(selectionNode);

			// write the content into xml file
			TransformerFactory transformerFactory = TransformerFactory
					.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(new File(gameName + ".xml"));

			// Output to console for testing
			// StreamResult result = new StreamResult(System.out);

			transformer.transform(source, result);

			System.out.println("File saved!");

		} catch (ParserConfigurationException pce) {
			pce.printStackTrace();
		} catch (TransformerException tfe) {
			tfe.printStackTrace();
		}
	}

}
